package data;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import operate.*;

import entry.DBMS;

import java.util.Set;

import operate.ConditionalExpression;
import storage.DataEntry;
import storage.DisplayField;
import storage.Field;

public class Tree {
	public static Map<String, LeafNode> leaves;
	public static List<InternalNode> inners;

	public static Set<String> getDisplayAccounts(List<Node> roots) {
		Set<String> set = new HashSet<String>();
		BufferedReader br = null;
		
		for (Node node : roots) {
			try {
				br = new BufferedReader(new InputStreamReader(new FileInputStream(node.getFile())));
				String line = null;
				while ((line = br.readLine()) != null) {
					set.add(line);
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				if (br != null) {
					try {
						br.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		}
		return set;
	}
	
	public static void setColumn() {
		List<Field> fields = null;
		Set<DisplayField> saveFields = null;
		for (Entry<String, LeafNode> e : Tree.leaves.entrySet()) {
			int col = 0;
			fields = DBMS.loadedTables.get(e.getKey()).getAttributes();
			saveFields = e.getValue().getSaveFields();
			for (Field field : fields) {
				for (DisplayField df1 : saveFields) {
					if (df1.getFieldName().equalsIgnoreCase(field.getFieldName())) {
						df1.setColumn(col);
						col += 1;
					}
				}
			}
		}
	}
	
	private static Node buildDKNode() {
		List<Node> list = new ArrayList<Node>();
		for (Node node : Tree.leaves.values()) {
			if (!node.isHasFather()) {
				list.add(node);
			}
		}
		if (Tree.inners.size() != 0) {
			for (Node node : Tree.inners) {
				if (!node.isHasFather()) {
					list.add(node);
				}
			}
		}
		while (list.size() > 1) {
			java.util.Collections.sort(list, new Comparator<Node>() {
				@Override
				public int compare(Node n1, Node n2) {
					return n1.getAccountNumbers() - n2.getAccountNumbers();
				}
			});
			
			Node left = list.remove(0);
			Node right = list.remove(0);
			Node inner = buildInternal(left, right, null);
			list.add(inner);
		}
		return list.get(0);
	}
	
	public static List<Node> buildTree(Select select) {
		List<Node> roots = new ArrayList<Node>();

		if (select.isExistWhereCondition()) {
			for (int i = 0; i < select.getConditions().size(); i++) {
				buildLeafNode(select, i);
				setColumn();
				
				roots.add(buildDKNode());//
			}
		}
		if (!select.isExistWhereCondition()) {
			buildLeafNode(select, 1);
			setColumn();
			//buildInternalNode(select, 1);
			roots.add(buildDKNode());//
		}
		return roots;
	}
	
	public static void buildLeafNode(Select select, int n) {

		Tree.leaves = new HashMap<String, LeafNode>();
		LeafNode leaf = null;

		for (String tempTable : select.getTableName()) {
			File file = new File(DBMS.currentPath + File.separator + tempTable + "_" + n + "$");
			Set<DisplayField> saveFields = new HashSet<DisplayField>();
			DisplayField df = null;
			int num = 0;
			if (!select.isSeeAll()) {
				for (DisplayField f : select.getAttributes()) {
					if (f.getTable().equals(tempTable)) {
						df = new DisplayField(f);
						if (saveFields.add(df)) {
							num += 1;
						}
					}
				}
			} else if (select.isSeeAll()) {
				for (Field field : DBMS.loadedTables.get(tempTable).getAttributes()) {
					df = new DisplayField(field, tempTable + "." + field.getFieldName(), tempTable);
					if (saveFields.add(df)) {
//						df.setColumn(num);
						num += 1;

					}
				} 
			}
			List<ConditionalExpression> conditions = new ArrayList<ConditionalExpression>();
			leaf = new LeafNode(true, file, false, null, num, saveFields, tempTable, false, conditions);// �µ�Ҷ���
			Tree.leaves.put(tempTable, leaf);
		}

		if (!select.isExistWhereCondition()) {
			return;
		}

		String name = null;
		String table = null;
		String attr = null;
		DisplayField df = null;
		List<ConditionalExpression> conditions = select.getConditions().get(n);
		for (ConditionalExpression condition : conditions) {
			if (condition.isLeftConstant() ^ condition.isRightConstant()) {

				name = condition.isLeftConstant() ? condition.getRight() : condition.getLeft();
				table = condition.isLeftConstant() ? condition.getRightTableName() : condition.getLeftTableName();
				attr = condition.isLeftConstant() ? condition.getRightAttribute() : condition.getLeftAttribute();

				if ((leaf = Tree.leaves.get(table)) != null) {
					leaf.setExistCondition(true);
					leaf.getConditions().add(condition);
					List<Field> list = null;
					try {
						list = DBMS.loadedTables.get(table).getAttributes();
					} catch (Exception e) {
						throw new RuntimeException("û��" + table + "�����");
					}
					boolean flag = false;
					for (Field field : list) {
						if (field.getFieldName().equals(attr)) {
							df = new DisplayField(field, name, table);
							if (leaf.getSaveFields().add(df)) {
								int i = leaf.getNumberField();
								leaf.setNumberField(i + 1);
							}
							flag = true;
						}
					}
					if (flag == false) {
						throw new RuntimeException("û��" + attr + "������ԣ�");
					}
				}
			} else {

				String leftTable = condition.getLeftTableName();

				if ((leaf = Tree.leaves.get(leftTable)) != null) {

					List<Field> list = null;
					try {
						list = DBMS.loadedTables.get(leftTable).getAttributes();
					} catch (Exception e) {
						throw new RuntimeException("û��" + table + "�����");
					}
					boolean flag = false;
					for (Field field : list) {
						if (field.getFieldName().equals(condition.getLeftAttribute())) {
							df = new DisplayField(field, condition.getLeft(), leftTable);
							if (leaf.getSaveFields().add(df)) {
								int i = leaf.getNumberField();
								leaf.setNumberField(i + 1);
							}
							flag = true;
						}
					}
					if (flag == false) {
						throw new RuntimeException("û��" + condition.getLeft() + "������ԣ�");
					}
				}

				String rightTable = condition.getRightTableName();

				if ((leaf = Tree.leaves.get(rightTable)) != null) {
					List<Field> list = null;
					try {
						list = DBMS.loadedTables.get(rightTable).getAttributes();
					} catch (Exception e) {
						throw new RuntimeException("û��" + rightTable + "�����");
					}
					boolean flag = false;
					for (Field field : list) {
						if (field.getFieldName().equals(condition.getRightAttribute())) {
							df = new DisplayField(field, condition.getRight(), rightTable);
							if (leaf.getSaveFields().add(df)) {
								int i = leaf.getNumberField();
								leaf.setNumberField(i + 1);
							}
							flag = true;
						}
					}
					if (flag == false) {
						throw new RuntimeException("û��" + condition.getRight() + "������ԣ�");
					}
				}
			}
		}
	}

	private static Node buildInternal(Node left, Node right, ConditionalExpression joinCondition) {
		InternalNode inner = null;
		int numberField = 0;
		int numberTable = 0;
		List<String> tableName = new ArrayList<String>();
		Set<DisplayField> saveFields = new HashSet<DisplayField>();

		if (left.isLeafNode) {
			LeafNode leftNode = (LeafNode) left;
			numberTable += 1;
			tableName.add(leftNode.getTableName());
		} else {
			InternalNode leftNode = (InternalNode) left;
			numberTable += leftNode.getNumberTable();
			tableName.addAll(leftNode.getTableName());
		}

		if (right.isLeafNode) {
			LeafNode rightNode = (LeafNode) right;
			numberTable += 1;
			tableName.add(rightNode.getTableName());
		} else {
			InternalNode rightNode = (InternalNode) right;
			numberTable += rightNode.getNumberTable();
			tableName.addAll(rightNode.getTableName());
		}

		for (DisplayField df : left.getSaveFields()) {
			if (joinCondition != null) {
				if (df.getFieldName().equals(joinCondition.getLeftAttribute())) {
					joinCondition.setLeftcolumn(df.getColumn());
				}
			}
			saveFields.add(new DisplayField(df));
		}

		DisplayField tdf = null;
		int step = left.getNumberField();
		for (DisplayField df : right.getSaveFields()) {
			tdf = new DisplayField(df);
			
			tdf.setColumn(tdf.getColumn() + step);
			saveFields.add(tdf);
			if (joinCondition != null) {
				if (df.getFieldName().equals(joinCondition.getRightAttribute())) {
					joinCondition.setRightcolumn(df.getColumn());
				}
			}
		}
		numberField = left.getNumberField() + right.getNumberField();
		File file = new File(
				DBMS.currentPath + File.separator + left.getFile().getName() + "&" + right.getFile().getName());
		inner = new InternalNode(false, file, false, null, numberField, saveFields, numberTable, tableName, true,
				joinCondition, left, right);
		if (joinCondition == null) {
			inner.setIsJoinOperation(false);
		}
		left.setFather(inner);
		left.setHasFather(true);
		right.setFather(inner);
		right.setHasFather(true);
		Tree.inners.add(inner);
		return inner;
	}

	public static void traverseTree(Node node) throws Exception {
		if (!node.isLeafNode) {
			traverseTree(((InternalNode) node).getLeftNode());
			traverseTree(((InternalNode) node).getRightNode());
			creatInnerFile((InternalNode) node);
		} else {
		}
	}

	public static void creatInnerFile(InternalNode node) {
		Node leftChild = node.getLeftNode();
		Node rightChild = node.getRightNode();

		ConditionalExpression condition = node.getJoinCondition();

		File leftFile = leftChild.getFile();
		File rightFile = rightChild.getFile();
		File tempFile = node.getFile();

		BufferedReader leftReader = null;
		BufferedReader rightReader = null;
		PrintWriter pw = null;
		
		List<String> lines = new ArrayList<String>();
		Hash hashTable = null;
		try {
			leftReader = new BufferedReader(new InputStreamReader(new FileInputStream(leftFile), "GBK"));
			rightReader = new BufferedReader(new InputStreamReader(new FileInputStream(rightFile), "GBK"));
			pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(tempFile), "GBK"));
			
			if (node.isIsJoinOperation()) {
				if (leftChild.getAccountNumbers() <= rightChild.getAccountNumbers()) {
					String type = condition.getLeftType();
					int column = condition.getLeftcolumn();
					String line = null;
					int index = 0;
					hashTable = new Hash(leftChild.getAccountNumbers());
					long hashCode = 0;
					Set<Integer> indexs = null;
					while ((line = leftReader.readLine()) != null) {
						lines.add(line);
						
						hashCode = "Integer".equalsIgnoreCase(type) ? Long.parseLong(line.split(",")[column]) : line.split(",")[column].hashCode();
						indexs = new HashSet<Integer>();
						indexs.add(index);
						hashTable.insert(new DataEntry(hashCode, indexs));
						index += 1;
					}
				} else {
					String type = condition.getRightType();
					int column = condition.getRightcolumn();
					String line = null;
					int index = 0;
					hashTable = new Hash(rightChild.getAccountNumbers());
					long hashCode = 0;
					Set<Integer> indexs = null;
					while ((line = rightReader.readLine()) != null) {
						lines.add(line);
						
						hashCode = "Integer".equalsIgnoreCase(type) ? Long.parseLong(line.split(",")[column]) : line.split(",")[column].hashCode();
						indexs = new HashSet<Integer>();
						indexs.add(index);
						hashTable.insert(new DataEntry(hashCode, indexs));
						index += 1;
					}
				}
				int accountNumbers = 0;
				if (leftChild.getAccountNumbers() <= rightChild.getAccountNumbers()) {
					String type = condition.getRightType();
					int column = condition.getRightcolumn();
					long hashCode = 0;
					String line = null;
					int pos;
					while ((line = rightReader.readLine()) != null) {
						hashCode = "Integer".equalsIgnoreCase(type) ? Long.parseLong(line.split(",")[column]) : line.split(",")[column].hashCode();
						pos = hashTable.search(hashCode);
						if (pos != -1) {
							for (int i : hashTable.getHashTable()[pos].getIndexs()) {
								pw.println(lines.get(i) + "," + line);
								accountNumbers += 1;
							}
						}
					}
				} else {
					String type = condition.getLeftType();
					int column = condition.getLeftcolumn();
					long hashCode = 0;
					String line = null;
					int pos;
					while ((line = leftReader.readLine()) != null) {
						hashCode = "Integer".equalsIgnoreCase(type) ? Long.parseLong(line.split(",")[column]) : line.split(",")[column].hashCode();
						pos = hashTable.search(hashCode);
						if (pos != -1) {
							for (int i : hashTable.getHashTable()[pos].getIndexs()) {
								pw.println(line + "," + lines.get(i));
								accountNumbers += 1;
							}
						}
					}
				}
				node.setAccountNumbers(accountNumbers);
			}
			if (!node.isIsJoinOperation()) {
				int accountNumbers = 0;
				String leftLine = null;
				String rightLine = null;
				while ((leftLine = leftReader.readLine()) != null) {
					while ((rightLine = rightReader.readLine()) != null) {
							pw.println(leftLine + "," + rightLine);
							accountNumbers += 1;
					}
					rightReader.close();
					rightReader = new BufferedReader(new InputStreamReader(new FileInputStream(rightFile), "GBK"));
				}
				node.setAccountNumbers(accountNumbers);
//				System.out.println(accountNumbers);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (leftReader != null) {
					leftReader.close();
				}
				if (rightReader != null) {
					rightReader.close();
				}
				if (pw != null) {
					pw.close();
				}
				if (!leftFile.delete()) {
					System.out.println(leftFile.getName() + " was not deleted");
				}
				if (!rightFile.delete()) {
					System.out.println(rightFile.getName() + " was not deleted");
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void display(Select select, Set<String> displayAccounts, Set<DisplayField> fields) {
		
		if (select.isSeeAll()) {
			for (String line : displayAccounts) {
				System.out.println(line);
			}
			System.out.println("Total lines:" + displayAccounts.size());
		} else {
			String[] attrs = null;
			for (DisplayField df : select.getAttributes()) {
				System.out.printf("%10s%10s\t", df.getType(), df.getFieldName());
			}
			System.out.println();// ����
			for (String line : displayAccounts) {
				attrs = line.split(",");
				for (DisplayField df1 : select.getAttributes()) {
					for (DisplayField df2 : fields) {
						if (df1.getName().equals(df2.getName())) {
							System.out.printf("%20s\t", attrs[df2.getColumn()]);
						}
					}
				}
				System.out.println();
			}
			System.out.println("Total lines: " + displayAccounts.size());
		}

	}
}
